Implement Export Text Feature

Here are the modifications required to implement the "Export Text" feature.

Please apply these changes within the <script> section of your code.

1. Add the New State Variable

Add the isExportTextMode flag near your other state variables at the beginning of the script.

JavaScript

        let currentBranch = [];
       let currentStepIndex = 0;
       let isEditingComment = false;
       let isAutoPlaying = false;
       let isExportTextMode = false; // Add this line

2. Update updateToolHighlights

Modify the updateToolHighlights function to disable all tool buttons when isExportTextMode is true.

JavaScript

            function updateToolHighlights() {
               const isModified = currentStepIndex > 0 || historyFEN.fen !== INITIAL_FEN;
               const highlights = {
                   'tool-new': isModified && !isExportTextMode,
                   'tool-open': !isExportTextMode,
                   'tool-edit': !isEditingComment && !isExportTextMode,
                   'tool-save': isModified && !isExportTextMode,
                   'tool-undo': undoStack.length > 1 && !isExportTextMode,
                   'tool-redo': redoStack.length > 0 && !isExportTextMode,
                   'tool-rotate': isRotateEnabled && !isExportTextMode,
                   'tool-flip': isFlipEnabled && !isExportTextMode,
                   'tool-exp-txt': !isExportTextMode,
                   'tool-exp-svg': !isExportTextMode
               };
               for (const [id, shouldEnable] of Object.entries(highlights)) {
                   const btn = document.getElementById(id);
                   if (btn) {
                       if (shouldEnable) {
                           btn.classList.add('enable');
                       } else {
                           btn.classList.remove('enable');
                       }
                   }
               }
           }

3. Block User Inputs During Export Mode

Protect all relevant event listeners by adding if (isExportTextMode) return;.

A. SVG Click Listener:

JavaScript

            svgElement.addEventListener('click', (event) => {
               if (isAnimating || isEditingComment || isExportTextMode) return; // Modified line

B. Step Controls:

JavaScript

            stepSlider.addEventListener('input', (e) => {
               if (isEditingComment || isExportTextMode) { // Modified line
                   e.preventDefault();
                   e.target.value = currentStepIndex;
                   return;
               }
               jumpToStep(parseInt(e.target.value, 10));
           });

           document.querySelector('.ejceesstepminus').addEventListener('click', () => {
               if (isEditingComment || isExportTextMode) return; // Modified line
               jumpToStep(currentStepIndex - 1);
           });

           document.querySelector('.ejceesstepplus').addEventListener('click', () => {
               if (isEditingComment || isExportTextMode) return; // Modified line
               jumpToStep(currentStepIndex + 1);
           });

           const startDiv = document.getElementById('record-start');
           if (startDiv) {
               startDiv.addEventListener('click', () => {
                   if (isExportTextMode) return; // Add this line
                   jumpToStep(0);
               });
           }

C. Comment Edit Toggle:

JavaScript

            document.querySelector('.ejceescomment').addEventListener('click', () => {
               if (isExportTextMode) return; // Add this line
               if (!isEditingComment) {
                   isEditingComment = true;
                   renderNoteUI();
               }
           });

D. Tool Button Listeners:

JavaScript

            const toolNewBtn = document.getElementById('tool-new');
           if (toolNewBtn) {
               toolNewBtn.addEventListener('click', () => {
                   if (isExportTextMode) return; // Add this line
                   // ... existing logic

JavaScript

            document.getElementById('tool-save').addEventListener('click', () => {
               if (isExportTextMode) return; // Add this line
               // ... existing logic

JavaScript

            document.getElementById('tool-open').addEventListener('click', () => {
               if (isExportTextMode) return; // Add this line
               document.getElementById('file-input').click();
           });

(Replace the existing undo/redo/rotate/flip bindings with these:)

JavaScript

            document.getElementById('tool-undo').addEventListener('click', () => {
               if (!isExportTextMode) undo();
           });
           document.getElementById('tool-redo').addEventListener('click', () => {
               if (!isExportTextMode) redo();
           });

           document.getElementById('tool-rotate').addEventListener('click', () => {
               if (isExportTextMode) return;
               isRotateEnabled = !isRotateEnabled;
               reapplyVisualPositions();
               updateToolHighlights();
           });

           document.getElementById('tool-flip').addEventListener('click', () => {
               if (isExportTextMode) return;
               isFlipEnabled = !isFlipEnabled;
               reapplyVisualPositions();
               updateToolHighlights();
           });

E. Keyboard Support:

JavaScript

            document.addEventListener('keydown', (e) => {
               if (isExportTextMode) return; // Add this line
               if (e.ctrlKey && e.key === 'z') {
                   e.preventDefault();
                   undo();
               }
               if (e.ctrlKey && e.key === 'y') {
                   e.preventDefault();
                   redo();
               }
           });

4. Implement Export & Import Logic

Add the following functions at the end of your script logic (inside DOMContentLoaded), which will handle generating the text, replacing the UI, parsing imported strings, and recreating the game sequence.

JavaScript

            // --- Text Export / Import Features ---

           // Extract the linear path corresponding to the currently selected branch
           function getGamePath() {
               let path = [];
               let currentNode = historyFEN;
               let branchPtr = 0;
               while (currentNode && currentNode.v && currentNode.v.length > 0) {
                   const choice = currentBranch[branchPtr] !== undefined ? currentBranch[branchPtr] : 0;
                   currentNode = currentNode.v[choice];
                   path.push(currentNode);
                   branchPtr++;
               }
               return path;
           }

           function generateExportText(isEnglish) {
               let path = getGamePath();
               let fen = historyFEN.fen;
               let text = fen + '\n';
               let isRedTurn = fen.split(' ')[1] === 'w';
               
               let moveIdx = 0;
               let moveNum = 1;
               
               while(moveIdx < path.length) {
                   let line = `${moveNum}. `;
                   if (!isRedTurn && moveIdx === 0) {
                       let blkNode = path[moveIdx];
                       let blkMove = isEnglish ? blkNode.move : NotationConverter.toChinese(blkNode.move);
                       line += `... ${blkMove}`;
                       moveIdx++;
                   } else {
                       let redNode = path[moveIdx];
                       let redMove = isEnglish ? redNode.move : NotationConverter.toChinese(redNode.move);
                       line += `${redMove}`;
                       moveIdx++;
                       
                       if (moveIdx < path.length) {
                           let blkNode = path[moveIdx];
                           let blkMove = isEnglish ? blkNode.move : NotationConverter.toChinese(blkNode.move);
                           line += ` ${blkMove}`;
                           moveIdx++;
                       }
                   }
                   text += line + '\n';
                   moveNum++;
               }
               return text.trim();
           }

           function renderExportTextUI() {
               const recordContainer = document.querySelector('.ejceesrecord');
               const commentDiv = document.querySelector('.ejceescomment');
               const btnDiv = document.querySelector('.ejceestextbtn');

               const cnText = generateExportText(false);
               const enText = generateExportText(true);

               recordContainer.innerHTML = `<textarea class="ejceescomment-edit" id="export-textarea" style="width:100%; height:100%; resize:none; border:none; outline:none; background:#2a2a2a; color:#fff; padding:8px; font-family:monospace; font-size:14px; white-space:pre; overflow:auto;">${cnText}</textarea>`;

               const cnBlob = new Blob([cnText], { type: 'text/plain' });
               const enBlob = new Blob([enText], { type: 'text/plain' });
               
               const now = new Date();
               const timestamp =
                   now.getFullYear().toString().padStart(4, '0') +
                   (now.getMonth() + 1).toString().padStart(2, '0') +
                   now.getDate().toString().padStart(2, '0') +
                   now.getHours().toString().padStart(2, '0') +
                   now.getMinutes().toString().padStart(2, '0') +
                   now.getSeconds().toString().padStart(2, '0');

               commentDiv.innerHTML = `
                   <div style="display:flex; flex-direction:column; gap:10px; padding:10px;">
                       <a href="${URL.createObjectURL(cnBlob)}" download="ejcees_fen_${timestamp}.txt" style="color:#66b2ff; text-decoration:none;">fen (${cnBlob.size} bytes)</a>
                       <a href="${URL.createObjectURL(enBlob)}" download="ejcees_en_fen_${timestamp}.txt" style="color:#66b2ff; text-decoration:none;">en_fen (${enBlob.size} bytes)</a>
                   </div>
               `;

               btnDiv.innerHTML = `
                   <div class="ejceestextbtninner">
                       <div class="note-btn btn-confirm" id="exp-confirm" title="Confirm">
                           <svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
                       </div>
                       <div class="note-btn btn-cancel" id="exp-cancel" title="Cancel">
                           <svg viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
                       </div>
                   </div>
               `;

               document.getElementById('exp-cancel').addEventListener('click', () => {
                   isExportTextMode = false;
                   renderRecordUI();
                   renderNoteUI();
                   updateToolHighlights();
               });

               document.getElementById('exp-confirm').addEventListener('click', () => {
                   const text = document.getElementById('export-textarea').value;
                   isExportTextMode = false;
                   importExportedText(text);
               });
           }

           function importExportedText(text) {
               text = text.trim();
               if (!text) {
                   renderRecordUI();
                   renderNoteUI();
                   updateToolHighlights();
                   return;
               }

               // Treat as JSON structure
               if (text.startsWith('{')) {
                   try {
                       const data = JSON.parse(text);
                       function expand(node, parentFen) {
                           let dc, simfen, newNode;
                           if (node.m) {
                               dc = deriveCoordsFromMove(parentFen, node.m);
                               simfen = simulateMove(parentFen, dc);
                               newNode = {
                                   fen: simfen,
                                   move: node.m,
                                   lastMove: dc,
                                   c: node.c || "",
                                   v: []
                               };
                           } else {
                               newNode = { fen: parentFen, c: node.c || "", v: [] };
                           }
                           if (node.v) {
                               newNode.v = node.v.map(child => expand(child, newNode.fen));
                           }
                           return newNode;
                       }
                       historyFEN = expand(data, data.fen);
                       initBranch();
                       currentStepIndex = 0;
                       renderRecordUI();
                       renderNoteUI();
                       jumpToStep(0);
                       saveStateToUndo();
                       return;
                   } catch (e) {
                       console.log("JSON parse failed, falling back to text format.");
                   }
               }

               // Process standard text notation format
               const fenMatch = text.match(/([a-zA-Z0-9/]+ [wb])/);
               if (!fenMatch) {
                   alert("Invalid format: FEN not found.");
                   renderRecordUI();
                   renderNoteUI();
                   updateToolHighlights();
                   return;
               }
               
               let baseFen = fenMatch[1];
               let lines = text.split('\n');
               let movesList = [];
               let currentIsRed = baseFen.includes(' w');

               for (let line of lines) {
                   line = line.trim();
                   if (/^\d+\./.test(line)) {
                       let movesStr = line.replace(/^\d+\.\s*/, '').trim();
                       let tokens = movesStr.split(/\s+/);
                       for (let token of tokens) {
                           if (token === '...') continue;
                           
                           let isEnglish = /^[a-zA-Z]/.test(token) || /^[+\-=1-9][a-zA-Z0-9]/.test(token);
                           let moveEn = token;
                           
                           if (!isEnglish) {
                               moveEn = NotationConverter.toEnglish(token, currentIsRed);
                           }
                           
                           movesList.push({ move: moveEn, isRed: currentIsRed });
                           currentIsRed = !currentIsRed;
                       }
                   }
               }

               let newHistoryFEN = {
                   fen: baseFen,
                   move: null,
                   lastMove: null,
                   c: "",
                   v: []
               };
               
               let currentNode = newHistoryFEN;
               let currentFen = baseFen;
               let hasError = false;

               for (let i = 0; i < movesList.length; i++) {
                   let m = movesList[i].move;
                   let isRed = movesList[i].isRed;
                   let dc = deriveCoordsFromMove(currentFen, m, isRed);
                   if (!dc) {
                       console.error("Failed to parse move notation:", m);
                       hasError = true;
                       break;
                   }
                   let nextFen = simulateMove(currentFen, dc);
                   
                   let newNode = {
                       fen: nextFen,
                       move: m,
                       lastMove: dc,
                       c: "",
                       v: []
                   };
                   currentNode.v.push(newNode);
                   currentNode = newNode;
                   currentFen = nextFen;
               }

               if (hasError && movesList.length > 0) {
                   alert("Parsed partially due to an invalid move notation.");
               }

               historyFEN = newHistoryFEN;
               initBranch();
               currentStepIndex = 0;
               renderRecordUI();
               renderNoteUI();
               jumpToStep(0);
               saveStateToUndo();
           }

           document.getElementById('tool-exp-txt').addEventListener('click', () => {
               if (isExportTextMode || isEditingComment) return;
               isExportTextMode = true;
               updateToolHighlights();
               renderExportTextUI();
           });